﻿using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;

using System.Windows;
using System.Windows.Documents; // FlowDocument, etc
using System.Windows.Media;
using System.Windows.Controls;

using System.Windows.Threading;
using System.Windows.Xps.Packaging;
using System.Windows.Xps;
using System.IO.Packaging;

namespace C4Tools.ML2
{
    /// <summary>
    /// Extracts movies from the changers attached to the given Control4 controller ,
    /// with progress reporting.
    /// </summary>
    internal class DocumentBuilder : ObservableWorker
    {
        List<C4Movie> allMovies;
        FlowDocument flowDoc;
        int nTables = 0;
        Window parent;
        System.IO.Stream docStream;
        Brush silverBrush;

        public DocumentBuilder(Window window, List<C4Movie> movies)
            : base()
        {
            this.parent = window;
            this.allMovies = movies;
        }


        /// <summary>
        ///   Produce a stream that contains the XPS that contains the FlowDocument.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///     This class produces a FlowDocument in a BackgroundWorker. We
        ///     want to return that thing to the caller, so it can display the
        ///     document.  But WPF does not allow an app to create a FlowDoc
        ///     on a bg thread and then use it (display it) on the UI
        ///     thread. The workaround is to save to a stream and
        ///     re-constitute it on the UI thread.  This method does that.
        ///   </para>
        ///   <para>
        ///     The caller actually loads the print-preview dialog from an XPS
        ///     document. So rather than saving as XAML here, this method
        ///     simply saves as an XPS document.  The caller then loads the
        ///     XPS document and ba-da-bing, displays it. No compression is
        ///     used in the saving of the XPS document, to save time.
        ///   </para>
        /// </remarks>
        private void ProduceXpsStream()
        {
            docStream = new System.IO.MemoryStream();

            Package p = Package.Open(docStream, FileMode.Create, FileAccess.ReadWrite);
            Uri documentUri = new Uri("hooboy://this-is-idiotic.xps"); // can be any damn uri
            PackageStore.AddPackage(documentUri, p);
            XpsDocument doc = new XpsDocument(p,
                                              CompressionOption.NotCompressed,
                                              documentUri.AbsoluteUri);

            // write the XPS document, and paginate while doing so.
            XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(doc);
            DocumentPaginator paginator = ((IDocumentPaginatorSource)flowDoc).DocumentPaginator;

            // Change the PageSize and PagePadding for the document
            // to match the CanvasSize for the printer device.
            paginator.PageSize = new Size(816, 1056);
            Thickness t = new Thickness(60); // 72 = 1in
            flowDoc.PagePadding = new Thickness(Math.Max(24, t.Left),
                                                Math.Max(6.4, t.Top),
                                                Math.Max(816 - (24 + 768), t.Right),
                                                Math.Max(1056 - (6 + 1000), t.Bottom));
            flowDoc.ColumnWidth = double.PositiveInfinity;
            writer.Write(paginator);
            doc.Close();   // must do to flush all bytes
            docStream.Position = 0;
        }


        public override void DoWork(object sender, DoWorkEventArgs e)
        {
            flowDoc = new FlowDocument();

            var block = new Paragraph(new Run("Home Theater movie listing, produced " +
                                              DateTime.Now.ToString("yyyy MMMM d HH:mm:ss tt")));
            block.FontSize = 12;
            flowDoc.Blocks.Add(block);

            allMovies.Sort(ByTitle);
            AddTable("Title");

            allMovies.Sort(ByGenre);
            AddTable("Genre");

            allMovies.Sort(ByYear);
            AddTable("Release Date");

            this.Stage = "Preparing the document for printing...";

            ProduceXpsStream();
        }


        private int ByTitle(C4Movie a, C4Movie b)
        {
            return a.Title.CompareTo(b.Title);
        }

        private int ByGenre(C4Movie a, C4Movie b)
        {
            if (a.Genre == b.Genre)
                return a.Title.CompareTo(b.Title);
            return a.Genre.CompareTo(b.Genre);
        }

        private int ByYear(C4Movie a, C4Movie b)
        {
            if (a.ReleaseDate == b.ReleaseDate)
                return a.Title.CompareTo(b.Title);
            return a.ReleaseDate.CompareTo(b.ReleaseDate);
        }


        private void AddTable(string label)
        {
            if (silverBrush == null)
            {
                var bc = new BrushConverter();
                silverBrush = (Brush)bc.ConvertFrom("#F8F8FF");
            }

            this.Stage = "Listing By " + label;
            this.Total = allMovies.Count;
            this.Current = 0;

            // Paragraph pageBreak = new Paragraph();
            // pageBreak.BreakPageBefore = true;
            // flowDoc.Blocks.Add(pageBreak);

            var block = new Paragraph(new Run("Movies By " + label));
            block.FontSize = 38;
            block.FontWeight = System.Windows.FontWeights.Bold;
            if (nTables > 0)
                block.BreakPageBefore = true;
            nTables++;
            flowDoc.Blocks.Add(block);

            var table1 = new Table();
            flowDoc.Blocks.Add(table1);

            // Set some global formatting properties for the table.
            table1.CellSpacing = 10;
            table1.LineHeight = 2;
            table1.LineStackingStrategy = LineStackingStrategy.MaxHeight;  // BlockLineHeight
            table1.Margin = new Thickness(0);

            table1.Background = System.Windows.Media.Brushes.White;

            int numberOfColumns = 6;
            for (int x = 0; x < numberOfColumns; x++)
            {
                table1.Columns.Add(new TableColumn());
            }

            table1.Columns[0].Width = new GridLength(32, GridUnitType.Pixel);
            table1.Columns[1].Width = new GridLength(38, GridUnitType.Pixel);
            table1.Columns[3].Width = new GridLength(68, GridUnitType.Pixel);
            table1.Columns[4].Width = new GridLength(84, GridUnitType.Pixel);
            table1.Columns[5].Width = new GridLength(124, GridUnitType.Pixel);
            //table1.Columns[3].Width = new GridLength ( 24 , GridUnitType.Pixel );

            // Create and add an empty TableRowGroup to hold the table's Rows.
            var trg = new TableRowGroup();
            table1.RowGroups.Add(trg);

            var tr = new TableRow();
            trg.Rows.Add(tr);

            // Global formatting for the header row.
            tr.Background = System.Windows.Media.Brushes.Silver;
            tr.FontSize = 16;
            tr.FontWeight = System.Windows.FontWeights.Bold;

            TableCell cell;
            var headings = new String[] {
                "#", "loc", "Title", "Year", "Genre", "Director(s)"
            };
            foreach (var h in headings)
            {
                cell = new TableCell(new Paragraph(new Run(h)));
                tr.Cells.Add(cell);
            }

            int count = 0;
            //var brush = System.Windows.Media.Brushes.White;
            foreach (var movie in allMovies)
            {
                tr = new TableRow();
                trg.Rows.Add(tr);
                tr.FontSize = 12;

                // alternate background colors
                tr.Background = (count % 2 == 0)
                    ? Brushes.White
                    : silverBrush;

                cell = new TableCell(new Paragraph(new Run("" + (count + 1))));
                //cell.BorderThickness= new Thickness(1, 1, 0, 1);
                tr.Cells.Add(cell);

                cell = new TableCell(new Paragraph(new Run(movie.Location)));
                tr.Cells.Add(cell);

                cell = new TableCell(new Paragraph(new Run(movie.Title)));
                //cell.BorderThickness= new Thickness(0, 1,0,0);
                tr.Cells.Add(cell);

                cell = new TableCell(new Paragraph(new Run(movie.ReleaseDate)));
                tr.Cells.Add(cell);

                cell = new TableCell(new Paragraph(new Run(movie.Genre)));
                tr.Cells.Add(cell);

                cell = new TableCell(new Paragraph(new Run(movie.Directors)));
                //cell.BorderThickness= new Thickness(0, 1, 1, 1);
                tr.Cells.Add(cell);

                count++;
                this.Current++;
            }
        }

        public System.IO.Stream DocStream
        {
            get
            {
                return docStream;
            }
        }

    }
}
